/* **********************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This source code is subject to terms and conditions of the Shared Source License
* for DSL Editor PowerToy. A copy of the license can be found in the License.htm file
* at the root of this distribution. If you can not locate the Shared Source License
* for DSL Editor PowerToy, please obtain a copy from: http://www.codeplex.com/dsltreegrideditor/Project/License.aspx.
* By using this source code in any fashion, you are agreeing to be bound by
* the terms of the Shared Source License for DSL Editor PowerToy.
*
* You must not remove this notice, or any other, from this software.
*
* **********************************************************************************/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Text;

namespace DslEditorPowerToy.ComponentModel.Design
{
    public class SolutionItemRelPathPickerEditor : SolutionItemPathPickerEditor
    {
        private const char PropertyNameDelimiter = '.';

        #region Public Methods
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            //Get full path to solution item
            string fullPath = base.EditValue(context, provider, value) as String;
            if (false == string.IsNullOrEmpty(fullPath))
            {
                //Get property name holding root path
                string propertyName = GetRelPathPropertyName(context.PropertyDescriptor);
                if (false == string.IsNullOrEmpty(propertyName))
                {
                    //Get value of property from instance
                    string rootPath = GetPropertyValue(context.Instance, propertyName);
                    if (false == string.IsNullOrEmpty(rootPath))
                    {
                        //Calculate relative path
                        return MakeRelPath(rootPath, fullPath);
                    }
                }
            }

            return string.Empty;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Builds a relative path to toPath from fromPath
        /// Assumes both paths have same common root
        /// </summary>
        /// <param name="rootPath"></param>
        /// <param name="fullPath"></param>
        /// <returns></returns>
        private string MakeRelPath(string fromPath, string toPath)
        {
            //Split both paths (directories only)
            string[] fromPaths = Path.GetDirectoryName(fromPath).Split(new char[] { Path.DirectorySeparatorChar },
                StringSplitOptions.RemoveEmptyEntries);
            string[] toPaths = Path.GetDirectoryName(toPath).Split(new char[] { Path.DirectorySeparatorChar },
                StringSplitOptions.RemoveEmptyEntries);

            int fromPartsCount = fromPaths.GetLength(0);
            int toPartsCount = toPaths.GetLength(0);
            int minPartsCount = System.Math.Min(fromPartsCount, toPartsCount);

            //Calculate common root (levels)
            int level = 0;
            for (int index = 0; index < minPartsCount; index++)
            {
                string fromPathPart = (string)fromPaths.GetValue(index);
                string toPathPart = (string)toPaths.GetValue(index);
                if (0 == string.Compare(fromPathPart, toPathPart, true))
                    level++;
                else
                {
                    //Paths differ
                    break; 
                }
            }

            //Calculate how many directory parts back up to common parent 
            int backCount = (fromPartsCount - level);

            StringBuilder relativePath = new StringBuilder();

            //Append directory navigation delimiters
            if (0 == backCount)
                relativePath.AppendFormat(@".{0}", Path.DirectorySeparatorChar);
            else
            {
                //Append back delimiters
                for (int counter = 0; counter < backCount; counter++)
                    relativePath.AppendFormat(@"..{0}", Path.DirectorySeparatorChar);
            }

            //Append rest of toPath
            if ((toPartsCount - level) > 0)
            {
                relativePath.AppendFormat(@"{0}{1}", string.Join(Path.DirectorySeparatorChar.ToString(),
                    toPaths, level, (toPartsCount - level)), Path.DirectorySeparatorChar);
            }
            //Append toPath filename (if any)
            relativePath.Append(Path.GetFileName(toPath));

            return relativePath.ToString();
        }
        /// <summary>
        /// Returns the value of the property (parts) for the specified instance
        /// </summary>
        /// <param name="p"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        private string GetPropertyValue(object instance, string propertyName)
        {
            //Break down property parts
            string[] propertyPaths = propertyName.Split(new char[] { PropertyNameDelimiter }, 
                StringSplitOptions.RemoveEmptyEntries);

            object instancePart = instance;
            object currentValue = null;
            foreach (string propertyPart in propertyPaths)
            {
                //Invoke property getter on current instance
                PropertyInfo propertyInfo = instancePart.GetType().GetProperty(propertyPart);
                if (null == propertyInfo)
                    return null;

                //Check there is a value to continue
                currentValue = propertyInfo.GetValue(instancePart, null);
                if (null == currentValue)
                    return null;

                //Advance pointer
                instancePart = currentValue;
            }

            //Check for string value
            if (currentValue is string)
                return currentValue as string;

            return null;
        }
        /// <summary>
        /// Returns the name of the property attribute
        /// </summary>
        /// <param name="propertyDescriptor"></param>
        /// <returns></returns>
        private string GetRelPathPropertyName(PropertyDescriptor descriptor)
        {
            //Search for known attributes (only one instance possible)
            foreach (Attribute attribute in descriptor.Attributes)
            {
                if (attribute is SolutionItemPickerRelPropertyAttribute)
                {
                    SolutionItemPickerRelPropertyAttribute propertyAttribute = attribute as SolutionItemPickerRelPropertyAttribute;
                    return propertyAttribute.PropertyName;
                }
            }

            return null;
        }
        #endregion
    }
}
